[アップデート] AWS App Meshが「仮想ゲートウェイ」を使ったメッシュ外部からのIngressアクセスをサポートしました
みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。
AWSが提供するメッシュサービス AWS App Mesh において、新しい機能「仮想ゲートウェイ」(Virtual Gateway) がリリースされました。
AWS App Mesh launches ingress support with virtual gateways
これは「App Meshの使い方が大きく広がるんじゃないか?」と思える、結構ビッグなアップデートではないかと思います。
AWSブログでハンズオンも公開されていますので、こちらに沿って試してみたいと思います。
Introducing Ingress support in AWS App Mesh | Containers
ハンズオンで作成するApp Meshの構成
以下の2通りのハンズオンが用意されていますが、
- 「App Mesh」と「ECS」の組み合わせ
- 「App Mesh」と「EKS」の組み合わせ
今回は「ECS」を使ったハンズオンを試してみました。
ハンズオンを最初から最後まで通すと、以下のようなApp Meshの構成が作成されます。
(VPCなどのインフラリソースについては省略しています)
構成要素を右側から順に説明していきます。
ECSサービス (メッシュ化の対象となるコンテナアプリケーション)
「ColorTeller」と名付けられた4種類のシンプルなコンテナアプリケーションです。
(「4種類」と言いつつ図では「5個」ありますが、最後の「ColorGatewayService」については後ほど説明します)
HTTPプロトコル (ポートは「9080」) で待ち受けを行い、リクエストを受けるとサービスの名前に応じた「white」「blue」「black」「red」という文字列をそれぞれ返します。
なお、4種類の振る舞いをする別々のアプリケーション (ECSタスク定義) として定義されていますが、実体はGo言語で書かれた単一のコンテナイメージです。
(環境変数で与えられたパラメーターによって応答する文字列が決定されます)
アプリケーション自体は単一のシンプルなコンテナで動作しますが、App Meshに対応させるために Envoy コンテナを「サイドカー」としてアタッチする必要があります。
(ECSタスク定義に「app」「envoy」という2つのコンテナが含まれています)
Envoyコンテナを定義する際に、環境変数APPMESH_VIRTUAL_NODE_NAME
に仮想ノードを判別する以下の値を設定する必要があります。
mesh/<メッシュ名>/virtualNode/<仮想ノード名>
今回のハンズオンでは、設定する値は以下のようになります。(「ColorTellerWhiteService」の場合の例)
mesh/ColorApp-Ingress/virtualNode/colorteller-white-vn
App Meshのメッシュおよび各リソース
App Meshのメッシュ「ColorApp-Ingress」を定義して、メッシュ上に各リソースを作成します。
「仮想ノード」(Virtual Node)
4つのECSサービスと1対1で紐付けられている、4つの仮想ノードが定義されています。
仮想ノードのリスナーは、プロトコルが「http」、ポートが「9080」に設定されています。
また、「TLS」の設定もありますが、今回はTLSを使用しません。(理由は後述します)
仮想ノードに紐付けるECSサービスの検出は、colorteller-white.default.svc.cluster.local
といったDNS名によって行われます。
そのため、ECSサービスの作成時に「サービスの検出」(サービスディスカバリ) を設定する必要があります。
ECSサービスに対するヘルスチェックをパス「/ping」に対して行うよう設定されています。
(アプリケーションコンテナ側では「/ping」への要求に対して「HTTP 200」で応答するように実装されています)
「仮想ルーター」(Virtual Router) および「ルート」(Route)
2つの仮想ルーターが定義されています。
それぞれの仮想ルーターのルートは、以下のように定義されています。
- colorteller-vr-1
- 仮想ノード「colorteller-white-vn」および「colorteller-blue-vn」に対して重み「1:1」で振り分ける
- colorteller-vr-2
- 仮想ノード「colorteller-black-vn」および「colorteller-red-vn」に対して重み「1:1」で振り分ける
「仮想サービス」(Virtual Service)
2つの仮想サービスが定義されています。
それぞれの仮想サービスは、上で定義した仮想ノードがプロバイダに設定されています。
「仮想ゲートウェイ」(Virtual Gateway) および「ゲートウェイルート」(Gateway Route)
今回のアップデートで新たに追加されたリソースです。
メッシュの外部にあるAWSリソース (例えばELB) がメッシュの内部にあるリソースと通信する際の、文字通り「ゲートウェイ」の役割をします。
仮想ゲートウェイの設定には、以下の要素があります。
リスナー
仮想ゲートウェイに対する通信は「リスナー」として定義されます。
プロトコルは「HTTP」「HTTP/2」「gRPC」から選択可能で、今回は「HTTP」を使用します。
ポートは今回「9080」を使用します。
その他は「ヘルスチェック」「TLS」の設定がありますが、今回はいずれも使用しません。
ゲートウェイルート
仮想ゲートウェイからメッシュ内部のリソースに対する通信は「ゲートウェイルート」として定義します。
ルーティングのルールは、HTTPおよびHTTP/2の場合は「パスベース」となり、gRPCの場合は「サービス名」の指定になります。
今回は「HTTP」プロトコルを使用するため、パスベースのルーティングを行います。
- パスのプレフィクスが
/color1
と一致する場合:- 仮想サービス「colorteller-1.default.svc.cluster.local」へルーティング
- パスのプレフィクスが
/color2
と一致する場合:- 仮想サービス「colorteller-2.default.svc.cluster.local」へルーティング
これらの設定に加えて、仮想ゲートウェイが動作するための「実体」を用意する必要があります。
仮想ゲートウェイの「実体」= Envoyコンテナ
仮想ゲートウェイの「実体」は、以下のいずれかによって実行される「Envoy」です。
- ECSの「サービス」で実行されるEnvoyコンテナ
- EKS/Kubernetesの「サービス」で実行されるEnvoyコンテナ
- EC2インスタンスで実行されるEnovy
今回は「ECSサービス」のEnvoyコンテナを使用します。
仮想ノードに紐付く「ECSサービス」ではEnvoyはサイドカーとしてアプリケーションコンテナに付帯させる位置付けでしたが、仮想ゲートウェイではEnvoyコンテナが本体となることに注意してください。
今回のハンズオンでは、Envoyコンテナに加えて、ロギングのための「CloudWatch Agent」コンテナが追加されています。
仮想ノードの時と同様に、Envoyコンテナを定義する際に、環境変数APPMESH_VIRTUAL_NODE_NAME
に仮想ゲートウェイを判別する以下の値を設定する必要があります。
mesh/<メッシュ名>/virtualGateway/<仮想ゲートウェイ名>
今回のハンズオンでは、設定する値は以下のようになります。
mesh/ColorApp-Ingress/virtualGateway/colorgateway-vg
ハンズオンに沿って試してみた
それでは、ここからは実際にハンズオンを試していきます。
準備
AWS CLI
App Meshのアップデートに対応したバージョンへアップデートする必要があります。
- AWS CLI v2系: 2.0.30 以上
- AWS CLI v1系: 1.18.97 以上
今回は、以下のバージョンで試しました。
$ aws --version aws-cli/2.0.30 Python/3.7.3 Linux/4.4.0-18362-Microsoft botocore/2.0.0dev34
EC2キーペア
ハンズオンで環境構築を行うCloudFormationがEC2キーペアを参照しますので、予め用意しておいてください。
なお、今回のハンズオンではECSクラスターをFargateベースで作成しますので、EC2ホストインスタンスはありません。
ただし、ECSとは別に踏み台 (Bastion) としてEC2インスタンスを1台作成しますので、そちらでキーペアが必要になります。
Docker実行環境
ハンズオンの流れでは、使用するアプリケーションコンテナをソースコードとDockerfileからビルドして用意するようになっています。
ローカルPCでDockerが利用できる状態にしておきます。
もしローカルPCでDockerが使用できない場合は「Cloud9を利用する」「EC2インスタンスを起動してDockerをインストールする」などの代替手段でもOKです。
GitHubのサンプルリポジトリをローカルPCへコピー
今回のハンズオンは、GitHubでAWSが公開しているaws/aws-app-mesh-examples
リポジトリに含まれるコンテンツの一部となっています。
以下のコマンドでGitHubリポジトリをローカルPCへコピーします。
$ git clone https://github.com/aws/aws-app-mesh-examples.git
コピーしましたら、今回のハンズオンで使うディレクトリへ遷移します。
$ cd aws-app-mesh-examples/walkthroughs/howto-ingress-gateway
環境変数の設定
以降の作業で参照する環境変数を設定しておきます。
(設定の間違いや漏れがあるとスクリプト実行時にエラーとなりますので、しっかり確認しましょう)
AWSアカウントID、使用するリージョン、および (さきほど用意した) キーペア名:
$ export AWS_ACCOUNT_ID=123456789012 $ export AWS_DEFAULT_REGION=ap-northeast-1 $ export KEY_PAIR_NAME=<キーペア名>
AWSアカウントIDは以下のコマンドラインで取得しても構いません。
$ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account')
今回のハンズオンで使う各種の名前:
$ export ENVIRONMENT_NAME=AppMeshIngressExample $ export MESH_NAME=ColorApp-Ingress $ export SERVICES_DOMAIN="default.svc.cluster.local" $ export COLOR_TELLER_IMAGE_NAME="howto-ingress/colorteller"
「Envoyコンテナ」のイメージパス:
$ export ENVOY_IMAGE=840364872350.dkr.ecr.ap-northeast-1.amazonaws.com/aws-appmesh-envoy:v1.12.4.0-prod
※ 840364872350
の部分は、自分のAWSアカウントIDではなく、上記の通りに入力してください。(AWSが用意しているアカウントです)
イメージパスには「リージョン」および「バージョンタグ」が含まれます。
(上記は「東京リージョン」かつ「2020/07/14現在の最新バージョン」の場合のイメージパスとなります)
使用するリージョンに応じたECRリポジトリ、最新のバージョンタグについては、下記のAWSドキュメントを参照してください。
Envoy image - AWS App Mesh
ハンズオンのインフラ環境を作成
VPC環境を作成する
ECSクラスターを実行するVPC環境を作成します。
以下のようにスクリプトを実行します。
$ ./infrastructure/vpc.sh
スクリプトを実行すると、CloudFormationのスタックが作成され、各種リソースが作成されていきます。
スクリプトの最後にSuccessfully created/updated stack - AppMeshIngressExample-vpc
と出力されれば、作成は成功です。
これで、以下のリソースが作成されました。
- VPC
- インターネットゲートウェイ
- サブネット: パブリック × 2、プライベート × 2
- NATゲートウェイ × 2
- Elastic IP × 2 (NATゲートウェイ用)
- ルートテーブル
ECSクラスター、ECRリポジトリを作成する
以下のようにスクリプトを順に実行します。
$ ./infrastructure/ecs-cluster.sh
$ ./infrastructure/ecr-repositories.sh
VPC構築と同様に、スクリプトを実行するとCloudFormationのスタックが作成されます。
それぞれ、以下のように出力されれば、作成は成功です。
Successfully created/updated stack - AppMeshIngressExample-ecs-cluster
Successfully created/updated stack - AppMeshIngressExample-ecr-repositories
アプリケーションコンテナのビルドおよびECRリポジトリへのプッシュ
以下のスクリプトを実行すると、コンテナのビルドからECRリポジトリへのプッシュまで、一連の流れが一気に行われます。
$ GO_PROXY=direct ./src/colorteller/deploy.sh
アプリケーションのビルドにはGo言語が使われていますが、Dockerのマルチステージビルドが採用されているため、ローカルPCにGo言語環境は不要です。
Cloud9やEC2インスタンスを利用する場合は、スクリプトを実行する前に、ECRリポジトリへログインするための設定が行われていることを確認してください。
スクリプトの処理が最後まで正常に行われれば、以下のように出力されるはずです。
+ docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/howto-ingress/colorteller:latest The push refers to repository [123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/howto-ingress/colorteller] 57890d9c5d52: Pushed latest: digest: sha256:f567124307291ea2e51aac82cc59b0065d4e109cee4217502734097002308cbc size: 528
エラーとなる場合は、「Dockerビルド」「ECRへのログイン」「ECRリポジトリへのプッシュ」のステップのうち、どのステップで失敗しているのかを確認して、トラブルシュートするのがよいかと思います。
App Meshのリソースを作成
いよいよ今回のメインのApp Meshリソースを作成していきます。
ですが、その前に、ハンズオン資料で用意されている手順を一部変更したいと思います。
ハンズオンでは「TLS」を使用するようになっていますが・・・
サンプルリポジトリに用意されているハンズオンでは、App Meshのメッシュ内通信で「TLS」を使用するように構成されています。
TLSを使用する際に「プライベートTLS証明書」が必要となりますが、ハンズオンの手順では「AWS Certificate Manager プライベート認証機関」(ACM Private CA) を使って作成するように案内されています。
ACM Private CAの利用料金体系は「運用するプライベートCA 1つあたり 400 USD/月」+「発行した証明書の数に応じた料金」となっています。
料金 - AWS Certificate Manager | AWS
最低でも400ドルかかってしまうというのは、ハンズオンをちょっと試してみるのに使うのはツライ・・・
ということで、今回はTLSを使わずにハンズオンを進めることにしました。
※ ACM Private CAを使う方法以外にも「自前で認証局を立ててプライベート証明書を発行する」という方法などがあると思います。 機会があれば、そういった方法を使ったApp MeshのTLS構成を試してみたいと思います。
2020/07/15追記
ACM Private CAの料金体系について、以下のようなご指摘を頂きました。(ありがとうございます)
・初回利用時に30日間の無料トライアルあり
・利用期間が1か月未満の月については、日割りで料金が計算される
ということですので、そのうち改めてプライベート証明書を発行してTLSを試してみようと思います。
仮想ゲートウェイの定義ファイルを書き換える
仮想ゲートウェイの定義ファイルにTLSを使用する記述がありますので、これを書き換えます。
$ vi mesh/colorgateway-vg.json
変更前:
{ "spec": { "listeners": [ { "portMapping": { "port": 9080, "protocol": "http" }, "tls": { "mode": "STRICT", "certificate": { "acm": { "certificateArn": $CERTIFICATE_ARN } } } } ] } }
ハイライト部分 (8~15行目) を削除します。
変更後:
{ "spec": { "listeners": [ { "portMapping": { "port": 9080, "protocol": "http" } } ] } }
もう一つ定義ファイルがありますので、こちらも書き換えます。
$ vi mesh/colorgateway-vg-backendDefaults.json
変更前:
{ "spec": { "listeners": [{ "portMapping": { "port": 9080, "protocol": "http" }, "tls": { "mode": "STRICT", "certificate": { "acm": { "certificateArn": $CERTIFICATE_ARN } } } }], "backendDefaults": { "clientPolicy": { "tls": { "validation": { "trust": { "acm": { "certificateAuthorityArns": [ $ROOT_CA_ARN ] } } } } } } } }
ハイライト部分 (7~14行目および19~29行目) を削除します。
変更後:
{ "spec": { "listeners": [{ "portMapping": { "port": 9080, "protocol": "http" } }], "backendDefaults": { "clientPolicy": { } } } }
App Meshリソースを作成する
ファイルの修正が終わりましたら、スクリプトを実行します。
$ ./mesh/mesh.sh up
こちらのスクリプトは、CloudFormationではなくAWS CLIコマンドを使ってApp Meshリソースの作成を行います。
(ですので、AWS CLIのバージョンが古いとエラーになる場合があります)
特にエラーが出ることなくスクリプトが最後まで実行されればOKです。
ECSサービスをデプロイ
既に作成済みのECSクラスター上に、ECSの「サービスディスカバリ」「タスク定義」「サービス」を作成していきます。
また、仮想ゲートウェイに対してトラフィックを送る役目の「ELB (NLB)」についても、併せて作成します。
CloudFormationテンプレートを書き換える
NLBターゲットグループの定義の中に「TLS」に関する記述がありますので、こちらを書き換えます。
$ vi infrastructure/ecs-service.yaml
変更前:
WebTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 30 HealthCheckPort: 9080 HealthCheckProtocol: TCP HealthCheckTimeoutSeconds: 10 HealthyThresholdCount: 3 TargetType: ip Name: !Sub "${EnvironmentName}-web" Port: 443 Protocol: TLS UnhealthyThresholdCount: 3 TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 120 VpcId: 'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
ハイライト部分 (649~650行目) を以下のように書き換えます。
変更後:
WebTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 30 HealthCheckPort: 9080 HealthCheckProtocol: TCP HealthCheckTimeoutSeconds: 10 HealthyThresholdCount: 3 TargetType: ip Name: !Sub "${EnvironmentName}-web" Port: 9080 Protocol: TCP UnhealthyThresholdCount: 3 TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 120 VpcId: 'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
同じファイルの中で、もう1点、書き換えを行います。
NLBリスナーの定義の中に「TLS」に関する記述がありますので、こちらについても書き換えます。
外部 (インターネット) 向けロードバランサーですのでACMから無償で発行できるパブリック証明書が利用できますが、手順の簡略化のために今回はTLSを使わないものとしました。
変更前:
PublicLoadBalancerListener: Type: AWS::ElasticLoadBalancingV2::Listener DependsOn: - PublicLoadBalancer Properties: DefaultActions: - TargetGroupArn: !Ref WebTargetGroup Type: 'forward' LoadBalancerArn: !Ref 'PublicLoadBalancer' Port: 443 Protocol: TLS Certificates: - CertificateArn: !Sub '${LoadBalancerCertificateArn}'
ハイライト部分のうち、667~668行目の「Port」「Protocol」を書き換えて、669~670行目の「Certificate」セクションは削除します。
変更後:
PublicLoadBalancerListener: Type: AWS::ElasticLoadBalancingV2::Listener DependsOn: - PublicLoadBalancer Properties: DefaultActions: - TargetGroupArn: !Ref WebTargetGroup Type: 'forward' LoadBalancerArn: !Ref 'PublicLoadBalancer' Port: 80 Protocol: TCP
ECSサービスをデプロイする
以下のようにスクリプトを実行します。
$ ./infrastructure/ecs-service.sh
スクリプトを実行するとCloudFormationのスタックが作成されます。
以下のように出力されれば、作成は成功です。
Successfully created/updated stack - AppMeshIngressExample-ecs-service
最後に、踏み台サーバーのIPアドレスとNLBのエンドポイントFQDNが表示されますので、メモしておきましょう。
Bastion endpoint: xx.xx.xx.xx ColorApp Endpoint: http://AppMe-Publi-XXXXXXXXXXXXX-XXXXXXXXXXXXXXXX.elb.ap-northeast-1.amazonaws.com
アプリケーションに対してアクセスしてみる
これで全ての環境が準備できましたので、NLBのエンドポイントからアクセスしてみましょう。
まず、さきほど表示されたNLBエンドポイントFQDNを環境変数に設定します。
export COLORAPP_ENDPOINT=http://AppMe-Publi-XXXXXXXXXXXXX-XXXXXXXXXXXXXXXX.elb.ap-northeast-1.amazonaws.com
パスに「/color1」を指定してアクセスします。
$ curl "${COLORAPP_ENDPOINT}/color1/tell" white $ curl "${COLORAPP_ENDPOINT}/color1/tell" blue $ curl "${COLORAPP_ENDPOINT}/color1/tell" white $ curl "${COLORAPP_ENDPOINT}/color1/tell" blue $ curl "${COLORAPP_ENDPOINT}/color1/tell" blue $ curl "${COLORAPP_ENDPOINT}/color1/tell" blue
6回アクセスしてみたところ、「white」が2回、「blue」が4回という結果でした。
(1:1の加重ルーティングですが、完全に等確率でレスポンスが返る訳ではありません)
今度は、パスに「/color2」を指定してアクセスします。
$ curl "${COLORAPP_ENDPOINT}/color2/tell" black $ curl "${COLORAPP_ENDPOINT}/color2/tell" red $ curl "${COLORAPP_ENDPOINT}/color2/tell" black $ curl "${COLORAPP_ENDPOINT}/color2/tell" red $ curl "${COLORAPP_ENDPOINT}/color2/tell" red $ curl "${COLORAPP_ENDPOINT}/color2/tell" red
「black」または「red」のいずれかが返ってきました。
これで、「仮想ゲートウェイ」でパスベースの振り分けが行われること、「仮想ルーター」で重さによる振り分けが行われることが確認できました。
後片付け
ハンズオンが終わりましたら、作成したリソースを削除します。
CloudFormationで作成したリソースを削除します:
(ECRリポジトリを削除する前に、登録したイメージを削除する必要があることに注意してください)
$ aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-ecs-service
$ aws ecr delete-repository --force --repository-name $COLOR_TELLER_IMAGE_NAME $ aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-ecr-repositories
$ aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-ecs-cluster
$ aws cloudformation delete-stack --stack-name $ENVIRONMENT_NAME-vpc
App Meshの各種リソースを削除します:
$ ./mesh/mesh.sh down
おわりに
TLSに関して修正を行わざるを得なかったのは残念ですが、App Meshの新しい機能「仮想ゲートウェイ」を試すことができました。
※ 「内部通信だからTLSはそれほど重要ではないのでは」という意見があるかもしれませんが、昨今の「ゼロトラストネットワーク」の考え方や、App Mesh (サービスメッシュ) の利用目的の一つに「マルチテナント」等があることを考慮すると、TLSによるデータ転送中の暗号化は重要なファクターであると思います。
ハンズオンはEKSを使ったパターンも用意されていますので、機会があれば、次回はEKSを試してみたいと思います。